package com.mainbo.core.filter;

import com.mainbo.platform.uc.client.JwtToken;
import com.mainbo.platform.uc.client.LoginSuccessCallback;
import com.mainbo.platform.uc.utils.JwtUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.util.WebUtils;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Map;

/**
 * @Author xww
 * @Description //TODO
 * @Date 2020/3/16   9:14
 **/
public class JwtFormAutenticationFilter extends FormAuthenticationFilter {

    /**
     * 认证成功后回调
     */
    private LoginSuccessCallback successCallback;

    /**
     * ajax loginurl
     */
    private String ajaxLoginUrl;

    /**
     * 是否使用本地登录
     */
    private Boolean useLocalLogin = false;

    @Override
    protected void setFailureAttribute(ServletRequest request, AuthenticationException ae) {
        request.setAttribute(getFailureKeyAttribute(), ae);
    }

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        boolean islogin = isLoginRequest(request, response);
        if (islogin && !notAjaxRequest((HttpServletRequest) request)) {
            Subject subject = getSubject(request, response);
            if (subject.isAuthenticated()) { // 已登录
                subject.logout();
            }
        }

        return super.isAccessAllowed(request, response, mappedValue);
    }

    @Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
        if (notAjaxRequest((HttpServletRequest) request)) {
            return super.onLoginFailure(token, e, request, response);
        } else {
            try {
                response.setCharacterEncoding("UTF-8");
                response.getWriter().write("{\"statusCode\":\"300\", \"message\":\"\u767b\u5f55\u5931\u8d25\uff0c\u8bf7\u91cd\u8bd5\"}");
            } catch (IOException e1) {
                // do nothing
            }
        }
        return false;
    }

    @Override
    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
        if (notAjaxRequest((HttpServletRequest) request)) {
            super.onLoginSuccess(token, subject, request, response);
        } else {
            try {
                // JwtToken jwtTokens = (JwtToken) token;
                if (useLocalLogin == null || !useLocalLogin) {
                    JwtToken jwtToken = (JwtToken) token;
                    ((HttpServletRequest) request).getSession().setAttribute(JwtUtils.JWT_PARA_NAME, jwtToken.getJwt());
                    validateSuccess(String.valueOf(jwtToken.getPrincipal()), jwtToken.getInfos(), request, response);
                }

                response.setCharacterEncoding("UTF-8");
                response.getWriter().write("{\"statusCode\":\"200\", \"callbackType\":\"closeCurrent\",\"message\":\"\u767b\u5f55\u6210\u529f\"}");
            } catch (IOException e1) {
                // do nothing
            }
        }
        return false;
    }

    /**
     * 本地验证成功后调用
     */
    private void validateSuccess(String sub, Map<String, Object> params, ServletRequest request, ServletResponse response) {
        if (successCallback != null) {
            successCallback.onSuccess(sub, params, request, response);
        }
    }

    protected boolean notAjaxRequest(HttpServletRequest request) {
        String requestedWith = request.getHeader("X-Requested-With");
        return requestedWith == null || !requestedWith.toLowerCase().contains("xmlhttprequest");
    }

    /**
     * Convenience method merely delegates to
     * {@link WebUtils#saveRequest(javax.servlet.ServletRequest)
     * WebUtils.saveRequest(request)} to save the request state for reuse later.
     * This is mostly used to retain user request state when a redirect is
     * issued to return the user to their originally requested url/resource.
     * <p/>
     * If you need to save and then immediately redirect the user to login,
     * consider using
     * {@link #saveRequestAndRedirectToLogin(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
     * saveRequestAndRedirectToLogin(request,response)} directly.
     *
     * @param request
     *          the incoming ServletRequest to save for re-use later (for
     *          example, after a redirect).
     */
    @Override
    protected void saveRequest(ServletRequest request) {
        if (request instanceof HttpServletRequest) {
            HttpServletRequest rq = (HttpServletRequest) request;
            String requestedWith = rq.getHeader("X-Requested-With");
            if (requestedWith == null || !requestedWith.toLowerCase().contains("xmlhttprequest")) {
                WebUtils.saveRequest(request);
            }
        } else {
            super.saveRequest(request);
        }

    }

    /**
     * Setter method for property <tt>successCallback</tt>.
     *
     * @param successCallback
     *          value to be assigned to property successCallback
     */
    public void setSuccessCallback(LoginSuccessCallback successCallback) {
        this.successCallback = successCallback;
    }

    /**
     * Getter method for property <tt>ajaxLoginUrl</tt>.
     *
     * @return property value of ajaxLoginUrl
     */
    public String getAjaxLoginUrl() {
        return ajaxLoginUrl;
    }

    /**
     * Setter method for property <tt>ajaxLoginUrl</tt>.
     *
     * @param ajaxLoginUrl
     *          value to be assigned to property ajaxLoginUrl
     */
    public void setAjaxLoginUrl(String ajaxLoginUrl) {
        this.ajaxLoginUrl = ajaxLoginUrl;
    }

    @Override
    protected boolean isLoginRequest(ServletRequest request, ServletResponse response) {
        return pathsMatch(getLoginUrl(), request) || pathsMatch(getAjaxLoginUrl(), request);
    }

    /**
     * Getter method for property <tt>useLocalLogin</tt>.
     *
     * @return useLocalLogin Boolean
     */
    public Boolean getUseLocalLogin() {
        return useLocalLogin;
    }

    @Override
    public String getLoginUrl() {
        if (useLocalLogin != null && useLocalLogin) {
            return this.ajaxLoginUrl;
        }
        return super.getLoginUrl();
    }

    /**
     * Setter method for property <tt>useLocalLogin</tt>.
     *
     * @param useLocalLogin
     *          Boolean value to be assigned to property useLocalLogin
     */
    public void setUseLocalLogin(Boolean useLocalLogin) {
        this.useLocalLogin = useLocalLogin;
    }

    @Override
    protected AuthenticationToken createToken(String username, String password, boolean rememberMe, String host) {
        if (useLocalLogin != null && useLocalLogin) {
            return super.createToken(username, password, rememberMe, host);
        }

        JwtToken token = new JwtToken(null, null);
        token.setUsername(username);
        token.setPassword(password != null ? password.toCharArray() : null);
        token.setRememberMe(rememberMe);
        token.setHost(host);
        return token;
    }
}
